/**
 * Copyright (C) 2009-2012, KingHool.com
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see http://www.gnu.org/licenses.
 */
package com.kinghool.smodel.io.bean;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.URI;
import java.net.URL;
import java.util.Date;

import com.kinghool.ebos.commons.utils.Assert;
import com.kinghool.ebos.commons.utils.Utils;
import com.kinghool.ebos.commons.vo.ITextable;
import com.kinghool.smodel.io.bean.xml.ArrayElementBeanFieldModel;
import com.kinghool.smodel.io.bean.xml.AttributeBeanFieldModel;
import com.kinghool.smodel.io.bean.xml.AutoBeanFieldModel;
import com.kinghool.smodel.io.bean.xml.BeanFieldModel;
import com.kinghool.smodel.io.bean.xml.BeanModel;
import com.kinghool.smodel.io.bean.xml.ChildBeanFieldModel;
import com.kinghool.smodel.io.bean.xml.TextBeanFieldModel;

/**
 * 
 * @Author Allen Deng(dengtailin@gmail.com) 
 */
public abstract class BaseBeanChannelProvider implements IBeanChannelProvider {

	protected Class<?> beanClass;
	
	public BaseBeanChannelProvider(Class<?> beanClass) {
		this.beanClass = beanClass;
	}
	
	public abstract BeanModel getBeanModel(Class<?> beanClass);
	
	public BeanChannel getBeanChannel() {
		return doGetBeanChannel(beanClass, getBeanModel(beanClass));
	}
	
	public Class<?> getClassType() {
		return beanClass;
	}
	
	protected BeanChannel doGetBeanChannel(Class<?> beanClass, BeanModel beanModel) {		
		BeanChannel channel = new BeanChannel(beanClass);
		if (!beanClass.getName().equals(beanModel.getClassName())) {
			return channel;
		}
		
		boolean includeAll = true;
		
		if (beanModel != null) {
			includeAll = beanModel.isIncludeAll();
			String rootId = beanModel.getName();
			if (Assert.isNotNullNotEmpty(rootId)) {
				channel.setRootId(rootId);
			}
		}
		
		for (Field field : Utils.getDeclaredFieldsRecursively(beanClass)) {
			boolean isExcludeExist = false;
			boolean isIncludeExist = false;
			BeanFieldModel bfModel = beanModel.getBeanField(field.getName());
			
			if (bfModel != null) {
				isExcludeExist = bfModel.isExcludeExist();
				isIncludeExist = bfModel.isIncludeExist();
			}
			
			if (isExcludeExist || (!includeAll && !isIncludeExist)) {
				continue;
			}
			
			if (bfModel != null) {
				if (bfModel instanceof AttributeBeanFieldModel) {
					AttributeBeanFieldModel attribute = (AttributeBeanFieldModel) bfModel;
					String name = attribute.getName();
					if (Assert.isNullOrEmpty(name)) {
						name = field.getName();
					}
					String child = attribute.getChild();
					
					if (Assert.isNullOrEmpty(child)) {
						channel.propertyAsAttribute(field.getName(), name);
					} else {
						channel.propertyAsChildAttribute(field.getName(), child, name);
					}
				} else if (bfModel instanceof TextBeanFieldModel) {
					TextBeanFieldModel txt = (TextBeanFieldModel) bfModel;
					String child = txt.getChild();
					if (Assert.isNullOrEmpty(child)) {
						channel.propertyAsText(field.getName());
					} else {
						channel.propertyAsChildText(field.getName(), child);
					}
				} else if (bfModel instanceof ChildBeanFieldModel) {
					ChildBeanFieldModel element = (ChildBeanFieldModel) bfModel;
					boolean isText = isTextObject(field.getType());
					
                    String child = element.getChild();
						
					if (Assert.isNullOrEmpty(child)) {
						child = field.getName();
					}
					
					if (isText) {
						channel.propertyAsChildText(field.getName(), child);					
					} else {
						BeanModel childBeanModel = element.getChildBeanModel();
						if (childBeanModel != null) {
							channel.propertyAs(field.getName(), child, doGetBeanChannel(field.getType(), childBeanModel));	
						}				
					}
				} else if (bfModel instanceof ArrayElementBeanFieldModel) {
					if (!Assert.isArray(field.getType())) {
						throw new IllegalArgumentException("array object is needed, such as[List, Set, Object[]].");
					}
					
					ArrayElementBeanFieldModel arrayElement = (ArrayElementBeanFieldModel) bfModel;

					String container = arrayElement.getContainer();
					String child = arrayElement.getChild();
					String get = arrayElement.getGet();
					String set = arrayElement.getSet();
					String add = arrayElement.getAdd();
					String clear = arrayElement.getClear();

					if (Assert.isNullOrEmpty(get)) {
						get = getDefaultGetArrayMethodName(field.getName());
					}
					if (Assert.isNullOrEmpty(set)) {
						set = getDefaultSetArrayMethodName(field.getName());
					}
					
					if (Assert.isNullOrEmpty(child)) {
						child = getDefaultArrayChildFullName(field.getName());
					}
					
					Class<?> fieldClazz = getArrayFieldType(field);
					
					boolean isText = isTextObject(fieldClazz);
					if (isText) {
						channel.arrayPropertyAsChildText(field.getName(), child, field.getType());
					} else {
						BeanModel childBeanModel = arrayElement.getChildBeanModel();
						if (childBeanModel != null) {
							channel.arrayPropertyAs(field.getName(), child, doGetBeanChannel(fieldClazz, childBeanModel));
						}
					}
					
					channel.arrayPropertyNodeName(field.getName(), container);				
					channel.arrayPropMethods(field.getName(), add, set, get, clear);
				} else if (bfModel instanceof AutoBeanFieldModel) {
					//use default
					if (Assert.isArray(field.getType())) {
						Class<?> fieldClazz = getArrayFieldType(field);
						if (fieldClazz != null) {
							boolean isText = isTextObject(fieldClazz);
							String childName = getDefaultArrayChildFullName(field.getName());
							
							if (isText) {
								channel.arrayPropertyAsChildText(field.getName(), childName, fieldClazz);
							} else {
								channel.arrayPropertyAs(field.getName(), childName, doGetBeanChannel(fieldClazz, getAutoChildBeanModel(fieldClazz)));
							}
						}
					} else {
						boolean isText = isTextObject(field.getType());
						
						if (isText) {
							channel.propertyAsChildText(field.getName(), field.getName());
						} else {
							channel.propertyAs(field.getName(), field.getName(), doGetBeanChannel(field.getType(), getAutoChildBeanModel(field.getType())));
						}
					}
				}
			} 
			
			
		}
		
		return channel;
	}
	
	protected abstract BeanModel getAutoChildBeanModel(Class<?> clazz);
	
	protected static Class<?> getArrayFieldType(Field field) {
		Type fieldType = field.getGenericType();
		Class<?> fieldClazz = null;
		if (fieldType instanceof ParameterizedType) {
			Type[] types = ((ParameterizedType)fieldType).getActualTypeArguments();
			if (types.length == 1) {
				if (types[0] instanceof Class<?>) {
					fieldClazz = (Class<?>) types[0];
				}
			}
		} else if (Object[].class.isAssignableFrom(field.getType())) {
			fieldClazz = field.getDeclaringClass();
		}
		return fieldClazz;
	}
		
	protected static boolean isTextObject(Class<?> clazz) {
		if (Number.class.isAssignableFrom(clazz) || String.class.equals(clazz)
				|| Date.class.isAssignableFrom(clazz)
				|| URI.class.isAssignableFrom(clazz)
				|| URL.class.isAssignableFrom(clazz)
				|| ITextable.class.isAssignableFrom(clazz)
				|| boolean.class.equals(clazz) 
				|| byte.class.equals(clazz)
				|| char.class.equals(clazz) 
				|| short.class.equals(clazz)
				|| int.class.equals(clazz) 
				|| double.class.equals(clazz)
				|| float.class.equals(clazz) 
				|| long.class.equals(clazz)) {
			return true;
		}
		
		return false;
	}
	
	protected static Field getFieldSilently(Class<?> clazz, String fieldName) {
		if (Assert.isNotNullNotEmpty(fieldName) && clazz != null) {
			try {
				return clazz.getDeclaredField(fieldName);
			} catch (Exception e) {
				//
			}
		}
		return null;
	}
	 
	protected static String getDefaultGetArrayMethodName(String fieldName) {
		return "get" + Utils.upStringFirstChar(fieldName);
	}
	
	protected static String getDefaultSetArrayMethodName(String fieldName) {
		return "set" + Utils.upStringFirstChar(fieldName);
	}
	 
	
	protected static String getDefaultArrayChildFullName(String fieldName) {
		if (fieldName.length() > 1
			&& fieldName.endsWith("s")) {
			return fieldName.substring(0, fieldName.length() - 1);
		}
		return fieldName;
	}
}
